﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;

namespace JavaSubstitutes
{
	public static class WriteableBitmapExtensions
	{
		public delegate void WriteableBitmapLoaded(object state, WriteableBitmap bmp);

		public static Graphics getGraphics(this WriteableBitmap wbmp)
		{
			//basically, a nop
			return new Graphics();
		}

		public static void drawImage(this WriteableBitmap dest, Image sourceImg, double destX, double destY)
		{
			int x = (int)destX, y = (int)destY;
			dest.drawImage(sourceImg, x, y, null);
		}

		public static void drawImage(this WriteableBitmap dest, Image sourceImg, double destX, double destY, Transform transform)
		{
			//consider: is this any better than simply calling bmp.Render  with translate xform? That seemed to work alright.
			dest.Invalidate();

			WriteableBitmap bmpImg = new WriteableBitmap((int)sourceImg.ActualWidth, (int)sourceImg.ActualHeight);
			bmpImg.Render(sourceImg, transform);
			bmpImg.Invalidate();
			if (destX < 0)
			{
				bmpImg.FlipHorizontal();
				destX *= -1; //reset it to a positive value
			}

			if (destY < 0)
			{
				bmpImg.FlipVertical();
				destY *= -1; //reset it to a positive value
			}

			dest.Blit(
				new Rect(destX, destY, sourceImg.ActualWidth, sourceImg.ActualHeight),  //dest rect 
				bmpImg,  //to blit to the dest wbmp
				new Rect(0, 0, sourceImg.ActualWidth, sourceImg.ActualHeight)); //source

			dest.Invalidate();
		}

		public static void drawImage(this WriteableBitmap wbmp, WriteableBitmap bmpToDraw, double x, double y)
		{
			wbmp.Invalidate();
			bmpToDraw.Invalidate();
			wbmp.Blit(new Rect(0, 0, wbmp.PixelWidth, wbmp.PixelHeight), bmpToDraw, new Rect(x, y, bmpToDraw.PixelWidth, bmpToDraw.PixelHeight));
			wbmp.Invalidate();
		}

		/// <summary>
		/// Flips around the X axis
		/// </summary>
		public static void FlipHorizontal(this WriteableBitmap wbmp)
		{
			//CANNOT get the scaletransform to render when the scale value's negative, so until that's sorted out
			//we'll do it by hand
			int width = wbmp.PixelWidth, height = wbmp.PixelHeight; //after examining the IL-cache the values so the CLR doesn't call them as much
			if (height == 0 || width == 0) return;

			for (int y = 0; y < height; y++)
				for (int x = 0; x < width / 2; x++)
				{
					int lhsPixel = wbmp.Pixels[(y * width) + x],
						rhsPixel = wbmp.Pixels[(y * width) + (width - x - 1)];
					if(lhsPixel==rhsPixel) continue;

					wbmp.Pixels[(y * width) + x] = rhsPixel;
					wbmp.Pixels[(y * width) + (width - x - 1)] = lhsPixel;
				}
		}

		/// <summary>
		/// Flips around the Y axis
		/// </summary>
		public static void FlipVertical(this WriteableBitmap wbmp)
		{
			//CANNOT get the scaletransform to render when the scale value's negative, so until that's sorted out
			//we'll do it by hand
			int width = wbmp.PixelWidth, height = wbmp.PixelHeight; //after examining the IL-cache the values so the CLR doesn't call them as much
			if (height == 0 || width == 0) return;

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height / 2; y++) //flip the column
				{
					int topPixel = wbmp.Pixels[(y * width) + x];
					int bottomPixel = wbmp.Pixels[((height - y) * width) - (width - x)];
					if (topPixel == bottomPixel) continue; //nothing to do- might as well NOT write back to the bitmap- is it expensive?

					wbmp.Pixels[(y * width) + x] = bottomPixel;
					wbmp.Pixels[((height - y) * width) - (width - x)] = topPixel;
				}
		}


		public static void drawImage(this WriteableBitmap wbmp, Image sourceImg, int x, int y, int width, int height)
		{
			wbmp.Invalidate();
			//example call:   dest.drawImage(sheet[xPic, yPic],  xPixel + (xFlipPic ? wPic : 0),  yPixel + (yFlipPic ? hPic : 0),  xFlipPic ? -wPic : wPic,  yFlipPic ? -hPic : hPic, null);

			//flip the image, then flip it back
			//http://www.wintellect.com/CS/blogs/jprosise/archive/2009/03/25/more-on-silverlight-3-s-new-writeable-bitmap.aspx

			//ScaleTransform transform = GetTransformForWidthHeight(ref width, ref height);
			//if (transform != null)
			//{
			//    img.RenderTransform = transform;
			//    img.RenderTransformOrigin = new Point(img.Width / 2, img.Height / 2);
			//}

			if (x < 0 || y < 0)
			{
				if (x < 0)
				{
					//clamp X at 0 and make it so that the width is adjusted
					x = 0;
					width += x;
				}

				if (y < 0)
				{
					//clamp X at 0 and make it so that the width is adjusted
					y = 0;
					height += y;
				}
			}

			WriteableBitmap bmpCrop = new WriteableBitmap((int)sourceImg.ActualWidth, (int)sourceImg.ActualHeight);
			bmpCrop.Render(sourceImg, null);
			bmpCrop.Crop(0, 0, width, height);
			bmpCrop.Invalidate();

			wbmp.drawImage(bmpCrop, x, y); // 0,0?
			bmpCrop.Invalidate();
		}

		private static ScaleTransform GetTransformForWidthHeight(ref int width, ref int height)
		{
			if (width >= 0 && height >= 0) return null;

			ScaleTransform transform = new ScaleTransform();
			if (width < 0)
			{
				//set the scale to reverse the image
				transform.ScaleX = -1;

				//now eliminate the negative width, which was the original reason we knew that it wanted it to flip the image
				width *= -1;
			}
			if (height < 0)
			{
				transform.ScaleY = -1;
				height *= -1;
			}

			return transform;
		}

		//protected virtual ScaleTransform GetTransformForWidthHeight(int width, int height)
		//{
		//    if (width >= 0 && height >= 0) return null;

		//    ScaleTransform transform = new ScaleTransform();
		//    if (width < 0)
		//    {
		//        transform.ScaleX = -1;
		//        width *= -1;
		//    }
		//    if (height < 0)
		//    {
		//        transform.ScaleY = -1;
		//        height *= -1;
		//    }

		//    return transform;
		//}

		private static void OnFailed(object sender, ExceptionRoutedEventArgs args)
		{
			throw new Exception("Couldn't load image: " + args.ErrorException.ToString());
		}

		public static void LoadWriteableBitmap(Uri source, double sourceWidth, double sourceHeight, Canvas layoutRoot, object state, EventHandler<RoutedEventArgs> onOpened, EventHandler<ExceptionRoutedEventArgs> onFailed, WriteableBitmapLoaded loaded)
		{
			BitmapImage bitmapImage = new BitmapImage() { CreateOptions = BitmapCreateOptions.None, UriSource = source };

			bitmapImage.ImageOpened += onOpened;
			bitmapImage.ImageFailed += onFailed;

			Image img = new Image();
			img.Visibility = Visibility.Visible;
			img.ImageFailed += (sender, args) => { Debug.WriteLine("failed to load image"); };

			WriteableBitmap bmp;
			img.SizeChanged += (sender, args) =>
				{
					bmp = new WriteableBitmap(10, 10);
					bmp.drawImage(img, 10, 10);

					bmp.Invalidate();
					layoutRoot.Children.Remove(img);
					loaded(state, bmp);
				};

			img.Source = bitmapImage;
			layoutRoot.Children.Add(img);
		}

		//this is really a method that MOVES a portion of the bmp by an offset, filling it with transparency
		public static void copyAndMoveArea(this WriteableBitmap wbmp, int x, int y, int width, int height, int xCamD, int yCamD)
		{
			//it looks like the purpose of java's CopyArea is to take a rectangle and then draw it at xCamD and yCamD
			//http://download.oracle.com/javase/1.4.2/docs/api/java/awt/Graphics.html

			//nothing to do
			if (xCamD == 0 && yCamD == 0) return;

			//get the part to move
			WriteableBitmap cropped = wbmp.Crop(x, y, width, height);

			//nuke the existing part and we'll redraw the cropped part to the new position
			wbmp.Clear();

			//add the cropped part to the bmp
			//consider: is a translationTransform the best way to get it moved??
			wbmp.drawImage(cropped, xCamD, yCamD);
		}

		public static void clearRect(this WriteableBitmap wbmp, int x, int y, int width, int height)
		{
			wbmp.FillRectangle(x, y, x + width, y + height, Colors.Transparent);
		}

		//public static void Clear(this WriteableBitmap dest)
		//{
		//    dest.fillRect(0, 0, dest.PixelWidth, dest.PixelHeight, Colors.Transparent);
		//}

		public static void fillRect(this WriteableBitmap wbmp, int x, int y, int width, int height, Color color)
		{
			wbmp.FillRectangle(x, y, x + width, y + height, color);
		}


		public static void setColor(this WriteableBitmap wbmp, Color color)
		{
			//uh- nothing to do?
			return;
		}

		//dest.translate(-xCam, -yCam);
		public static void translate(this WriteableBitmap wbmp, int xCam, int yCam)
		{
			//?
		}

		//TODO: man this is an ugly way to pass in points... java doesn't have a Point structure?
		public static void fillPolygon(this WriteableBitmap wbmp, int[] pointsX, int[] pointsY, int length, Color color)
		{
			int[] points = new int[(length + 1) * 2]; //+1 to close it off
			for (int i = 0; i < length; i++)
			{
				points[2 * i] = pointsX[i];
				points[(2 * i) + 1] = pointsY[i];
			}

			points[length] = pointsX[0];
			points[length + 1] = pointsY[0];

			wbmp.FillPolygon(points, color);
		}


	}
}
